﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Zeta;
using Zeta.Common;
using Zeta.Internals;

//using YAMB.Common;



namespace D3.Pathing
{

    public class Path
    {
        public Queue<SpeedyAStarNode> Queue;
        private Map _map;
        private SpeedyAStar _cubscout;

        private List<SpeedyAStarNode> _walkedNodes;
        private System.Drawing.Point _sCur;
        private System.Drawing.Point _sGoal;
        private System.Drawing.Point _sStart;
        private bool _infallable = false;

        public bool IsValid = false;

        public int[] D3ToMapCoords(float x, float y)
        {
            return _map.D3ToMapCoords(x, y);
        }

        public float[] MapToD3Coords(int x, int y)
        {
            return _map.MapToD3Coords(x, y);
        }

        public int Count()
        {
            return Queue.Count;
        }

        public void DrawMe()
        {
            foreach (var pf in Queue)
            {
                //Draw.DrawRectangle(_map.drawingCoords(pf.X, pf.Y)[0], _map.drawingCoords(pf.X, pf.Y)[1],
                //    _map.drawingCoords(pf.X, pf.Y)[0] + 2, _map.drawingCoords(pf.X, pf.Y)[1] + 2, 0xFFFF69B4);
            }

        }

        private heurs _heurs;

        public Path(float x2, float y2, SpeedyAStar cubscout, Map map, heurs h) : this(ZetaDia.Actors.Me.Position.X, ZetaDia.Actors.Me.Position.Y, x2, y2, cubscout, map,  h)
        {

        }


        public System.Drawing.Point getCurNode()
        {
            return _sCur;
        }

        public System.Drawing.Point getCurNodeInD3Coords()
        {
            float[] tmp = MapToD3Coords(_sCur.X,_sCur.Y);
            return new Point((int)tmp[0], (int)tmp[1]);
        }

        public Path(float x1, float y1, float x2, float y2)
        {
            IsValid = true;
            Queue = new Queue<SpeedyAStarNode>();
            _walkedNodes = new List<SpeedyAStarNode>();
            _map = Multiverse.GetCurrentMap();

            int[] p1 = _map.D3ToMapCoords(x1, y1);
            int[] p2 = _map.D3ToMapCoords(x2, y2);

            _sStart = new System.Drawing.Point(p1[0], p1[1]);
            _sGoal = new System.Drawing.Point(p2[0], p2[1]);

            SpeedyAStarNode start = new SpeedyAStarNode();
            start.X = p1[0];
            start.Y = p1[1];

            SpeedyAStarNode goal = new SpeedyAStarNode();
            start.X = p2[0];
            start.Y = p2[1];

            Queue.Enqueue(start);
            Queue.Enqueue(goal);
        }

        public Path(float x1, float y1, float x2, float y2, SpeedyAStar cubscout, Map map, heurs heurs)
        {
            _heurs = heurs;
            _cubscout = cubscout;
            _map = map;

            _walkedNodes = new List<SpeedyAStarNode>();

            int[] p1 = _map.D3ToMapCoords(x1, y1);
            int[] p2 = _map.D3ToMapCoords(x2, y2);


            _sStart = new System.Drawing.Point(p1[0],p1[1]);
            _sGoal  = new System.Drawing.Point(p2[0],p2[1]);

            Logging.Write(_sStart.X + "/" + _sStart.Y + " -> " + _sGoal.X + "/" + _sGoal.Y);

            int a = Environment.TickCount;
            List<SpeedyAStarNode> pf = cubscout.FindPath(_sStart, _sGoal,_heurs);

            if (pf != null)
            {
                pf.Reverse();
                Queue = new Queue<SpeedyAStarNode>(pf);
                IsValid = true;
            }
        }

        public SpeedyAStarNode GetNextNode()
        {
            if (!IsValid)
            {                
                Recompute();
            }

            lock(this)
            {

            var s = Queue.Peek();

            _sCur = new System.Drawing.Point(s.X,s.Y);

            _walkedNodes.Add(s);

            float[] rCoords = _map.MapToD3Coords(_sCur.X, _sCur.Y);
            return Queue.Dequeue();
            }
        }

        public void Recompute()
        {



            int[] p1 = _map.D3ToMapCoords(ZetaDia.Actors.Me.Position.X, ZetaDia.Actors.Me.Position.Y);


            _sStart = new System.Drawing.Point(p1[0], p1[1]);

            int a = Environment.TickCount;
            List<SpeedyAStarNode> pf = _cubscout.FindPath(_sStart, _sGoal,_heurs);

            Logging.Write(_sStart.X + "/" + _sStart.Y + " -> " + _sGoal.X + "/" + _sGoal.Y);

            if (pf == null)
            {
                return;
            }
                IsValid = true;
                pf.Reverse();
                lock (this)
                {
                    Queue = new Queue<SpeedyAStarNode>(pf);
                }

            if (Queue.Count > 2)
            {
                Queue.Dequeue();
                Queue.Dequeue();
            }

            _sCur = new System.Drawing.Point(Queue.Peek().X, Queue.Peek().Y);
        }

        public void Validate(int x, int y)
        {          
           if (Queue.Any(xz => xz.X+2 >= x && xz.X-2 <= x && xz.Y+2 >= y  && xz.Y-2 <= y)) 
                IsValid = false;
        }
    }

    public class Map
    {

        private bool _collisionUpdate = false;
        private const int MaximumHardness = 2;
        private const float CoordScale = 2.5f;
        private readonly SpeedyAStar _cubscout;
        private int _worldId;

        private readonly Dictionary<System.Drawing.Point, System.Drawing.Point> border;
        //private readonly PathingLogger logger = new PathingLogger("D3Pathing.Map.Log");
        private readonly List<SpeedyAStarNode> _walkedNodes;

        private List<Path> _paths;

        private int _blocked;

        private void Swap<T>(ref T a, ref T b)
        {
            T c = a;
            a = b;
            b = c;
        }

        public List<Point> BresenhamLine(int x0, int y0, int x1, int y1)
        {
            // Optimization: it would be preferable to calculate in

            // advance the size of "result" and to use a fixed-size array

            // instead of a list.

            List<Point> result = new List<Point>();

            bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0);
            if (steep)
            {
                Swap(ref x0, ref y0);
                Swap(ref x1, ref y1);
            }
            if (x0 > x1)
            {
                Swap(ref x0, ref x1);
                Swap(ref y0, ref y1);
            }

            int deltax = x1 - x0;
            int deltay = Math.Abs(y1 - y0);
            int error = 0;
            int ystep;
            int y = y0;
            if (y0 < y1) ystep = 1; else ystep = -1;
            for (int x = x0; x <= x1; x++)
            {
                if (steep) result.Add(new Point(y, x));
                else result.Add(new Point(x, y));
                error += deltay;
                if (2 * error >= deltax)
                {
                    y += ystep;
                    error -= deltax;
                }
            }

            return result;
        }

        public bool IsTileWalkable(int x, int y)
        {
            return (Collisions[x, y] == 0);
        }

        public Map(int WorldId)
        {
            _worldId = WorldId;
            _paths = new List<Path>();
            CachedScenes = new Dictionary<int, CachedScene>();
            border = new Dictionary<System.Drawing.Point, System.Drawing.Point>();
            _walkedNodes = new List<SpeedyAStarNode>();

            Collisions = new byte[2048, 2048];

            for (int i = 0; i < 2048; i++)
            {
                for (int k = 0; k < 2048; k++)
                {
                    Collisions[i, k] = (byte) MaximumHardness;
                }
            }

            foreach (Scene s in ZetaDia.Scenes.GetScenes())
            {
                try {
                    if (s.SceneInfo.NavMeshDef.GridHeight != s.SceneInfo.NavMeshDef.GridWidth) continue;
                    var cs = new CachedScene(s);
                    LoadCollisions(cs);
                } catch (Exception e)
                {
                    Logging.Write(e.Message);   
                }
            }

            _cubscout = new SpeedyAStar(Collisions);
            SpeedyAStar.SetDPTolerance(1);
        }

        public void CheckForNewScenes()
        {
            if (_collisionUpdate)
            {
                Logging.Write("Were already in CollisionUpdate");
                return;
            }
            foreach (Scene s in ZetaDia.Scenes.GetScenes())
            {
                if (!CachedScenes.ContainsKey(s.SceneGuid))
                {
                    if (s.SceneInfo.NavMeshDef.GridHeight != s.SceneInfo.NavMeshDef.GridWidth) continue;
                    var cs = new CachedScene(s);
                    LoadCollisions(cs);
                }
            }
        }

        
        private Dictionary<int, CachedScene> CachedScenes { get; set; }

        private float Width { get; set; }
        private float Height { get; set; }

        private float X { get; set; }
        private float Y { get; set; }


        private int RelativeWidth { get; set; }
        private int RelativeHeight { get; set; }

        private byte[,] Collisions { get; set; }

        private byte[,] getCollision()
        {
            return Collisions;
        }

        public Dictionary<int, CachedScene> getCachedScenes()
        {
            return CachedScenes;
        }

        public void BlockRange()
        {
            int[] p = D3ToMapCoords(ZetaDia.Actors.Me.Position.X, ZetaDia.Actors.Me.Position.Y);
            int range = 7;

            for (int i = -range + 1; i < range; i++)
            {
                for (int k = -range + 1; k < range; k++)
                {
                    bool block = false;
                    for (int l = -1; l < 2; l++)
                    {
                        for (int m = - 1; m < 2; m++)
                        {
                            if (p[0] + l > 0 && p[1] + m > 0)
                                if (Collisions[p[0] + l, p[1] + m] != MaximumHardness) block = true;
                        }
                    }
                    if (block)
                    {
                        BlockNode(p[0] + i, p[1] + k, MaximumHardness, 0, 0, false);
                        //Collisions[p[0] + i, p[1] + k] = 0;
                    }
                }
            }
        }

        public Bitmap DrawMap()
        {

            Bitmap pt = new Bitmap(2048, 2048);

            Graphics g = Graphics.FromImage(pt);
            Pen my_pen = new Pen(Color.Black);
            Pen my_pen2 = new Pen(Color.Red);


            
            foreach (System.Drawing.Point p in border.Keys)
            {
                g.DrawRectangle(my_pen,drawingCoords(p.X, p.Y)[0], drawingCoords(p.X, p.Y)[1],1,1);
                //Draw.DrawRectangle(drawingCoords(p.X, p.Y)[0], drawingCoords(p.X, p.Y)[1],
                  //                 drawingCoords(p.X, p.Y)[0] + 1, drawingCoords(p.X, p.Y)[1] + 1, 0xFFFFFFFF);
            }

            //pt.Save("map.bmp");

            foreach (Path pf in _paths)
            {
                foreach (SpeedyAStarNode a in pf.Queue)
                {
                    g.DrawRectangle(my_pen2, drawingCoords(a.X, a.Y)[0], drawingCoords(a.X, a.Y)[1], 2, 2);
                }
            }

            var _p = D3ToMapCoords(1478, 2849);
            var _p2 = drawingCoords(_p[0], _p[1]);
            g.DrawRectangle(my_pen2, _p2[0], _p2[1], 5, 5);

            _p = D3ToMapCoords(2981, 2835);
            _p2 = drawingCoords(_p[0], _p[1]);
            g.DrawRectangle(my_pen2, _p2[0], _p2[1], 5, 5);

            //float[] px = drawingCoords(D3ToMapCoords(Me.X, Me.Y)[0], D3ToMapCoords(Me.X, Me.Y)[1]);

            pt.Save(_worldId+"-World.bmp");
            //Draw.DrawRectangle(px[0], px[1], px[0] + 5, px[1] + 5, 0xFFFF69B4);
            return pt;
        }

        public float[] drawingCoords(int x, int y)
        {
            var p = new float[2];
            p[1] = ((x) - X)*2;
            p[0] = ((y) - Y)*2f;
            /*p[1] = ((x*0.5f) - X*0.5f) + 100;
            p[0] = ((y*0.5f) - Y*0.5f) + 200;*/
            return p;
        }


        private void BlockNode(int x, int y, int hardness, int prevX, int prevY, bool stop)
        {
            if (hardness == 0 || x < 0 || y < 0) return;

            if (hardness == MaximumHardness) Collisions[x, y] = 0;

            if (Collisions[x, y] > 0) Collisions[x, y] = (byte) hardness;
            hardness = hardness - 1;

            var p = new System.Drawing.Point(x, y);

            if (border.Keys.Contains(p)) border.Remove(p);
            if (Collisions[x, y] == 1) border.Add(p, p);

            if (stop) return;

            stop = false;

            if (prevX != x - 1 && prevY != y + 1) BlockNode(x - 1, y + 1, hardness, x, y, stop);
            if (prevX != x && prevY != y + 1) BlockNode(x, y + 1, hardness, x, y, stop);
            if (prevX != x + 1 && prevY != y + 1) BlockNode(x + 1, y + 1, hardness, x, y, stop);
            if (prevX != x - 1 && prevY != y) BlockNode(x - 1, y, hardness, x, y, stop);
            if (prevX != x + 1 && prevY != y) BlockNode(x + 1, y, hardness, x, y, stop);
            if (prevX != x - 1 && prevY != y - 1) BlockNode(x - 1, y - 1, hardness, x, y, stop);
            if (prevX != x && prevY != y - 1) BlockNode(x, y - 1, hardness, x, y, stop);
            if (prevX != x + 1 && prevY != y - 1) BlockNode(x + 1, y - 1, hardness, x, y, stop);
        }


        public int[] D3ToMapCoords(float x, float y)
        {
            var ret = new int[2];

            ret[0] = Convert.ToInt32((x)/CoordScale);
            ret[1] = Convert.ToInt32((y)/CoordScale);

            return ret;
        }

        public float[] MapToD3Coords(int x, int y)
        {
            var ret = new float[2];

            ret[0] = (x*CoordScale);
            ret[1] = (y*CoordScale);

            return ret;
        }


        public bool LoadCollisions(CachedScene cs)
        {
            _collisionUpdate = true;
            lock(this)
            {
                
            
            float additionalWidth = 0;
            float additionalHeight = 0;

            if (CachedScenes.ContainsKey(cs.SceneId)) return false;

            if (Width == 0 || Width < cs.X)
            {
                Width = cs.X;
                additionalWidth = cs.SizeX*CoordScale;
            }

            if (Height == 0 || Height < cs.Y)
            {
                Height = cs.Y;
                additionalHeight = cs.SizeY*CoordScale;
            }


            Width = (Width - X) + additionalWidth;
            Height = (Height - Y) + additionalHeight;

            RelativeWidth = Convert.ToInt32(Width/CoordScale);
            RelativeHeight = Convert.ToInt32(Height/CoordScale);

            CachedScenes.Add(cs.SceneId, cs);

            float x = cs.X;
            float y = cs.Y;

            int relativeX = 0;
            int relativeY = 0;

            if (x > 0)
            {
                relativeX = Convert.ToInt32(x/CoordScale);
            }
            if (y > 0)
            {
                relativeY = Convert.ToInt32(y/CoordScale);
            }

            if (X == 0 || X > relativeX)
            {
                X = relativeX;
            }

            if (Y == 0 || Y > relativeY)
            {
                Y = relativeY;
            }

            for (int x2 = 0; x2 < cs.SizeX; x2++)
            {
                for (int y2 = 0; y2 < cs.SizeY; y2++)
                {
                    int xn = relativeX + x2;
                    int yn = relativeY + y2;
                    if (!cs.Collisions[x2, y2].Flags.HasFlag(Zeta.Internals.SNO.NavCellFlags.AllowWalk) || cs.Collisions[x2, y2].Flags.HasFlag(Zeta.Internals.SNO.NavCellFlags.RoundedCorner0)
                        || cs.Collisions[x2, y2].Flags.HasFlag(Zeta.Internals.SNO.NavCellFlags.RoundedCorner1) || cs.Collisions[x2, y2].Flags.HasFlag(Zeta.Internals.SNO.NavCellFlags.RoundedCorner2)
                        || cs.Collisions[x2, y2].Flags.HasFlag(Zeta.Internals.SNO.NavCellFlags.RoundedCorner3))
                    {
                        foreach (Path p in _paths)
                        {
                            p.Validate(xn, yn);
                        }
                       
                        BlockNode(xn, yn, MaximumHardness, 0, 0, false);
                        _blocked++;
                    }
                }
            }

                _collisionUpdate = false;
            return true;
            }
        }

        public Path ComputePath(Point point, heurs heurs)
        {
            return ComputePath(ZetaDia.Actors.Me.Position.X, ZetaDia.Actors.Me.Position.Y, point.X, point.Y,heurs);
        }

        public Path ComputePath(float x, float y, heurs heurs)
        {
            return ComputePath(ZetaDia.Actors.Me.Position.X, ZetaDia.Actors.Me.Position.Y, x, y,heurs);
        }

        public Path ComputePath(float x1, float y1, float x2, float y2, heurs heurs)
        {
           var x = new Path(x1,y1,x2,y2,_cubscout,this, heurs);

             _paths.Add(x);
            return x;
        }

        #region Nested type: mPoint

        public struct mPoint
        {
            public int x;
            public int y;
        }

        #endregion
    }
}